# 文件 1
# 文件类型：py
# 文件名称：20250310 - TEMP.py
# 内容：
from DrissionPage import ChromiumPage, ChromiumOptions
import os
import time
import re
from datetime import datetime
import requests
import hashlib
import logging
import shutil

# 配置日志（仅打印到控制台）
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger('OnelapFitDownloader')

# 定义全局变量
STORAGE_DIR = r"/root/fit_files/"
#STORAGE_DIR = r"/Downloads/"
SUPPORTED_FORMATS = ['.fit', '.tcx', '.gpx']
MAX_FILES_PER_BATCH = 9  # 每批最多上传9个文件
MAX_FILE_SIZE = 10 * 1024 * 1024  # 10MB
MAX_TOTAL_SIZE = 50 * 1024 * 1024  # 50MB

ONELAP_ACCOUNT = "13671567424"
ONELAP_PASSWORD = "Knight!984"
IGPSPORT_ACCOUNT = "13671567424"
IGPSPORT_PASSWORD = "Knight!984"

# 定义函数
def create_retry_session():
    """创建带重试机制的会话"""
    logger.debug("创建带重试机制的会话")
    session = requests.Session()
    retry = requests.adapters.Retry(
        total=5,
        backoff_factor=0.3,
        status_forcelist=(500, 502, 504)
    )
    adapter = requests.adapters.HTTPAdapter(max_retries=retry)
    session.mount('http://', adapter)
    session.mount('https://', adapter)
    return session

def login_onelap(session, account, password):
    """登录顽鹿账号"""
    logger.info("尝试登录顽鹿账号")
    login_url = "https://www.onelap.cn/api/login"
    hashed_pwd = hashlib.md5(password.encode()).hexdigest()

    payload = {
        "account": account,
        "password": hashed_pwd
    }

    try:
        response = session.post(login_url, json=payload, timeout=10)
        response.raise_for_status()
        data = response.json()

        if data.get('code') != 200:
            logger.error(f"登录失败: {data.get('error', '未知错误')}")
            raise ValueError("API返回非200状态码")

        logger.info(f"登录成功 | 用户ID: {data['data'][0]['userinfo']['uid']} | 用户名：{data['data'][0]['userinfo']['nickname']}")
        return data['data'][0]
    except Exception as e:
        logger.error("登录过程中发生异常", exc_info=True)
        raise

def fetch_activities(session, token_data, latest_igpsport_activity):
    """获取活动列表数据"""
    logger.info("获取活动列表数据")
    headers = {
        'Cookie': f"ouid={token_data['userinfo']['uid']};"
                  f"XSRF-TOKEN={token_data['token']};"
                  f"OTOKEN={token_data['refresh_token']}"
    }

    try:
        response = session.get(
            "http://u.onelap.cn/analysis/list",
            headers=headers,
            timeout=10
        )
        response.raise_for_status()
        data = response.json()

        filtered = data['data']

        # 遍历 filtered 列表，查找匹配的记录
        matched_activity = None
        for activity in filtered:
            if activity.get('fitUrl'):
                fit_filename = activity['fitUrl'].split('.')[0]
            else:
                fit_filename = activity['fileKey'].split('.')[0]

            if fit_filename == latest_igpsport_activity:
                matched_activity = activity
                break

        if matched_activity:
            activities_after_matched = [
                activity for activity in filtered
                if activity['created_at'] > matched_activity['created_at']
            ]
            logger.info(f"筛选到 {len(activities_after_matched)} 个符合的活动")
            return activities_after_matched
        else:
            logger.warning("未找到匹配的活动记录")
            return []
    except Exception as e:
        logger.error("获取活动列表失败", exc_info=True)
        raise

def clear_storage_dir(directory):
    """清空指定文件夹中的所有文件和子文件夹"""
    logger.info(f"开始清空文件夹: {directory}")
    try:
        # 遍历文件夹中的所有文件和子文件夹
        for item in os.listdir(directory):
            item_path = os.path.join(directory, item)
            if os.path.isfile(item_path) or os.path.islink(item_path):
                os.unlink(item_path)  # 删除文件或链接
            elif os.path.isdir(item_path):
                shutil.rmtree(item_path)  # 删除子文件夹
        logger.info(f"文件夹已清空: {directory}")
    except Exception as e:
        logger.error(f"清空文件夹时发生错误: {e}", exc_info=True)

def download_fit_file(session, activity, headers):

    """下载 FIT 文件，并在下载前清空存储目录"""
    # 清空存储目录
    clear_storage_dir(STORAGE_DIR)

    """下载 FIT 文件"""


    download_url = activity["durl"]
    if not download_url.startswith("http"):
        download_url = f"http://u.onelap.cn{download_url}"

    if activity.get('fitUrl'):
        filename = f"{activity['fitUrl']}.fit"
    else:
        filename = f"{activity['fileKey']}"
    filepath = os.path.join(STORAGE_DIR, filename)

    if os.path.exists(filepath):
        logger.warning(f"文件已存在，跳过下载: {filename}")
        return

    try:
        logger.info(f"开始下载: {filename}")
        if "http://u.onelap.cn" in download_url:
            response = session.get(download_url, headers=headers, timeout=10, stream=True)
        else:
            response = session.get(download_url, headers=headers, timeout=10, stream=True)

        response.raise_for_status()

        os.makedirs(STORAGE_DIR, exist_ok=True)
        with open(filepath, 'wb') as f:
            for chunk in response.iter_content(chunk_size=8192):
                if chunk:
                    f.write(chunk)
        logger.info(f"文件下载完成: {filepath}")
    except Exception as e:
        logger.error(f"下载失败: {filename}", exc_info=True)
        if os.path.exists(filepath):
            os.remove(filepath)
            logger.warning(f"已删除不完整文件: {filepath}")

# 初始化浏览器选项
options = ChromiumOptions()
options.incognito()  # 启用匿名模式
#options.set_argument('--start-maximized')  # 浏览器启动时最大化
options.headless()  # 启用无头模式
options.set_argument("--headless=new")  # 显式添加无头参数
options.set_argument("--no-sandbox")    # 避免沙盒问题


# 启动浏览器并打开目标页面
tab = ChromiumPage(options)
tab.get('https://login.passport.igpsport.cn/login')

# 等待页面加载完成并输入登录信息
tab.wait.eles_loaded('@type=submit')
tab.ele('@id=basic_username').input(IGPSPORT_ACCOUNT)  # 输入用户名
tab.ele('@id=basic_password').input(IGPSPORT_PASSWORD)  # 输入密码
tab.ele('@type=submit').click()  # 点击登录按钮

# 等待页面加载并处理可能的弹窗
time.sleep(1)  # 等待页面加载完成
try:
    tab.ele('@style=width: 28px; height: 28px; position: absolute; z-index: 10; right: 159px; top: 194px; cursor: pointer;').click()  # 点击关闭弹窗（如果存在）
except Exception as e:
    pass

# 获取表格数据
cloumes = tab.eles('@class=ant-table-row ant-table-row-level-0')
igpsport_activities = []
for cloume in cloumes:
    lines = cloume.text.strip().split('\t')  # 去除首尾空白字符
    if len(lines) >= 5:  # 确保数据完整性
        parsed_data = {
            "file_name": lines[0].replace('\n', ''),
            "activity_date": lines[1],
            "distance": lines[2],
            "duration": lines[3],
            "average_speed": lines[4]
        }
        igpsport_activities.append(parsed_data)

# 筛选出以"Match"或"Magene"开头的记录
filtered_activities = [
    activity for activity in igpsport_activities
    if activity['file_name'].startswith(('MATCH', 'MAGENE'))
]

# 提取时间戳并排序
def extract_timestamp(file_name):
    """从文件名中提取时间戳"""
    try:
        if file_name.startswith('MATCH'):
            match = re.search(r'(\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2})', file_name)
            if match:
                return datetime.strptime(match.group(1), '%Y-%m-%d-%H-%M-%S')
        elif file_name.startswith('MAGENE'):
            match = re.search(r'_(\d{13})$', file_name)
            if match:
                return datetime.fromtimestamp(int(match.group(1)) / 1000)
    except Exception as e:
        logger.error(f"解析时间戳失败: {file_name} - {e}")
    return None

# 按时间戳降序排序
filtered_activities.sort(key=lambda x: extract_timestamp(x['file_name']), reverse=True)

# 获取最新的记录
latest_igpsport_activity = filtered_activities[0] if filtered_activities else None

if latest_igpsport_activity:
    logger.info(f"最新活动记录: {latest_igpsport_activity['file_name']}")
else:
    logger.warning("未找到符合条件的活动记录")
    tab.close()
    exit()

# 开始执行 FIT 文件下载任务
logger.info("===== 开始执行 FIT 文件下载任务 =====")
session = create_retry_session()
try:
    token_data = login_onelap(session, ONELAP_ACCOUNT, ONELAP_PASSWORD)
    activities = fetch_activities(session, token_data, latest_igpsport_activity['file_name'])

    logger.info(f"总共需要处理 {len(activities)} 个活动")
    for idx, activity in enumerate(activities, 1):
        logger.debug(f"正在处理第 {idx}/{len(activities)} 个活动")
        download_fit_file(
            session,
            activity,
            {'Cookie': session.headers.get('Cookie')}
        )

    logger.info("===== FIT 文件下载完成 =====")
except Exception as e:
    logger.critical("主流程发生致命错误", exc_info=True)
finally:
    session.close()
    logger.info("HTTP会话已关闭")

# 从文件夹中递归查找符合条件的文件
def get_valid_files(folder_path):
    """从指定文件夹中递归查找符合条件的文件"""
    valid_files = []
    for root, _, files in os.walk(folder_path):
        for file in files:
            file_path = os.path.join(root, file)
            file_extension = os.path.splitext(file)[1].lower()
            file_size = os.path.getsize(file_path)

            if file_extension in SUPPORTED_FORMATS and file_size <= MAX_FILE_SIZE:
                valid_files.append(file_path)
    return valid_files

# 获取符合条件的文件列表
valid_files = get_valid_files(STORAGE_DIR)
if not valid_files:
    logger.warning("没有找到符合条件的文件。")
    tab.close()
    exit()

# 将文件分批处理
def batch_files(file_list, batch_size):
    """将文件列表分批处理"""
    for i in range(0, len(file_list), batch_size):
        yield file_list[i:i + batch_size]

# 分批上传文件
for batch in batch_files(valid_files, MAX_FILES_PER_BATCH):
    # 点击上传按钮并上传文件
    time.sleep(1)  # 等待页面加载完成
    tab.wait.eles_loaded('@class=ant-btn css-1rwxm65 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid')
    upload_button = tab.ele('@class=ant-btn css-1rwxm65 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid')
    upload_button.click()  # 点击上传按钮

    # 等待上传区域加载并上传文件
    time.sleep(1)  # 等待上传区域加载完成
    tab.wait.eles_loaded('@class=ant-upload-drag-container')
    upload_area = tab.ele('@class=ant-upload-drag-container')

    for file_path in batch:
        upload_area.click.to_upload(file_path)  # 逐个上传文件
        time.sleep(1)  # 等待文件上传完成

    # 点击确认按钮
    time.sleep(1)  # 等待上传完成
    tab.wait.eles_loaded('@class=ant-btn css-1rwxm65 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ok-btn')
    ok_button = tab.ele('@class=ant-btn css-1rwxm65 ant-btn-primary ant-btn-color-primary ant-btn-variant-solid ok-btn')
    ok_button.click()  # 点击确认按钮

    # 等待页面加载并关闭页面
    time.sleep(5)  # 等待页面加载完成
    tab.wait.eles_loaded('@class=close')
    close_button = tab.ele('@class=close')
    close_button.click()  # 点击关闭按钮

# 关闭浏览器
tab.close()
logger.info("===== 任务执行完成 =====")
